﻿using Autofac;
using Autofac.Integration.WebApi;
using Owin;
using System.Linq;
using System.IO;
using System.Reflection;
using System.Web.Http;
using Yoze.Common.Messaging;
using Yoze.Common.Repositories;
using Yoze.DomainRepositories;
using Yoze.Common.Services;
using Microsoft.Owin.Hosting;
using System;
using System.Collections.Generic;
using Yoze.Common.Config;
using Yoze.Services.Common;

namespace Yoze.Service
{
    internal sealed class YozeService : Common.Services.Service
    {
        private readonly YozeConfiguration configuration = YozeConfiguration.Instance;

        private const string SearchPath = "services";

        private static List<IService> microServices = new List<IService>();

        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);


        private Dictionary<string, string> microConfigs = new Dictionary<string, string>();

        /// <summary>
        /// 寻找所有微服务
        /// </summary>
        /// <param name="builder"></param>
        private void DiscoverServices(ContainerBuilder builder)
        {
            var searchFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), SearchPath);
            foreach (var file in Directory.EnumerateFiles(searchFolder, "*.dll", SearchOption.AllDirectories))
            {
                try
                {
                    var assembly = Assembly.LoadFrom(file);
                    var exportedTypes = assembly.GetExportedTypes();
                    var microserviceRegisterType = exportedTypes.FirstOrDefault(x => x.IsSubclassOf(typeof(Autofac.Module)) &&
                        x.BaseType.GetGenericTypeDefinition() == typeof(MicroserviceRegister<>));
                    if (microserviceRegisterType != null)
                    {
                        var registeringService = microserviceRegisterType.BaseType.GetGenericArguments().First();
                        if (configuration.Services.All(x => x.Type != registeringService.FullName))
                        {
                            continue;
                        }
                        var mod = (Autofac.Module)Activator.CreateInstance(microserviceRegisterType, configuration);
                        builder.RegisterModule(mod);
                    }

                    if (exportedTypes.Any(t => t.IsSubclassOf(typeof(ApiController))))
                    {
                        builder.RegisterApiControllers(assembly).InstancePerRequest();
                    }
                }
                catch { }
            }
        }

        public void Configuration(IAppBuilder app)
        {

            var builder = new ContainerBuilder();

            builder.RegisterInstance<YozeConfiguration>(this.configuration).SingleInstance();

            builder.Register(x => new Messaging.RedisMq.RedisMqCommandPublisher(configuration.CommandQueue.ConnectionUri, configuration.CommandQueue.ExchangeName))
                .As<ICommandPublisher>();

            builder.Register(x => new Messaging.RedisMq.RedisMqEventPublisher(configuration.EventQueue.ConnectionUri, configuration.EventQueue.ExchangeName))
                .As<IEventPublisher>();

            builder.RegisterType<Messaging.RedisMq.RedisMqMessageSubscriber>()
                .Named<IMessageSubscriber>("CommandSubscriber");

            builder.RegisterType<Messaging.RedisMq.RedisMqMessageSubscriber>()
                .Named<IMessageSubscriber>("EventSubscriber");

            var redisSetting = new RedisSetting
            {
                ConnectionString = configuration.RedisEventStore.ConnectionString,
                CollectionName = configuration.RedisEventStore.CollectionName,
                DatabaseName = configuration.RedisEventStore.Database
            };

            builder.Register(x => new RedisDomainRepository(redisSetting, x.Resolve<IEventPublisher>())).As<IDomainRepository>();

            // Discovers the services.
            DiscoverServices(builder);

            // Register the API controllers within the current assembly.
            builder.RegisterApiControllers(this.GetType().Assembly);

            // Create the container by builder.
            var container = builder.Build();

            // Register the services.
            microServices.AddRange(container.Resolve<IEnumerable<IService>>());

            app.UseAutofacMiddleware(container);

            HttpConfiguration config = new HttpConfiguration();

            config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            app.UseAutofacWebApi(config);
            app.UseWebApi(config);
        }

        public override void Start(object[] args)
        {
            // Validate the applicatoin configuration
            if (string.IsNullOrEmpty(configuration?.ApplicationSetting?.Url))
            {
                throw new YozeConfigurationException("配置中未指定URL.");
            }

            if (string.IsNullOrEmpty(configuration?.CommandQueue?.ConnectionUri))
            {
                throw new YozeConfigurationException("未在配置文件中指定命令队列的连接URI.");
            }

            if (string.IsNullOrEmpty(configuration?.CommandQueue?.ExchangeName))
            {
                throw new YozeConfigurationException("未在配置文件中指定命令队列名.");
            }

            if (string.IsNullOrEmpty(configuration?.EventQueue?.ConnectionUri))
            {
                throw new YozeConfigurationException("未在配置文件中指定事件队列的连接URI.");
            }

            if (string.IsNullOrEmpty(configuration?.EventQueue?.ExchangeName))
            {
                throw new YozeConfigurationException("未在配置文件中指定事件队列的名称.");
            }

            var url = configuration.ApplicationSetting.Url;

            using (WebApp.Start<YozeService>(url: url))
            {
                microServices.ForEach(ms =>
                {
                    log.Info($"Starting microservice '{ms.GetType().FullName}'...");
                    ms.Start(args);
                });
                log.Info("Yoze Service started successfully.");
                Console.ReadLine();
            }
        }
    }
}
